package com.wei.czz.framework.blog.helper;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.wei.czz.common.dto.blog.comment.CommentDto;
import com.wei.czz.common.dto.blog.comment.CommentExcel;
import com.wei.czz.common.dto.common.NameValue;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.enums.common.exportTask.TaskTypeEnum;
import com.wei.czz.common.enums.blog.comment.CommentEnum;
import com.wei.czz.common.enums.common.FileTypeEnum;
import com.wei.czz.common.enums.common.exportTask.ExportStatusEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.property.DownloadProperty;
import com.wei.czz.common.tool.PageDto;
import com.wei.czz.common.utils.CopyUtils;
import com.wei.czz.common.utils.SecurityUtils;
import com.wei.czz.common.utils.TimeUtils;
import com.wei.czz.common.vo.common.exportTask.ExportTaskVo;
import com.wei.czz.common.vo.blog.comment.CommentFormVo;
import com.wei.czz.framework.blog.service.BlogCommentService;
import com.wei.czz.framework.common.entity.ExportTaskEntity;
import com.wei.czz.framework.common.service.ExportTaskService;
import com.wei.czz.framework.rabbitMQ.message.task.ExportTaskMessage;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.Validator;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2024-04-15 20:21:06
 * className: BlogCommentHelper
 * version: 1.0
 * description:
 */
@Component
@AllArgsConstructor
public class BlogCommentHelper {

    private static final Logger log = LoggerFactory.getLogger(BlogCommentHelper.class);

    private final DownloadProperty downloadProperty;

    private final BlogCommentService blogCommentService;

    private final ExportTaskService exportTaskService;

    private final Validator validator;

    public void downloadCommentList(ExportTaskEntity exportTask, HttpServletResponse response) {

        // 导出文件类型
        String fileType; int type;
        if (exportTask.getFileType().equals(FileTypeEnum.XLSX.getValue())) {
            fileType = ".xlsx";
            type = 0;
        } else if (exportTask.getFileType().equals(FileTypeEnum.CSV.getValue())) {
            fileType = ".csv";
            type = 1;
        } else if (exportTask.getFileType().equals(FileTypeEnum.XLS.getValue())) {
            fileType = ".xls";
            type = 0;
        } else if (exportTask.getFileType().equals(FileTypeEnum.TXT.getValue())) {
            fileType = ".txt";
            type = 2;
        } else {
            log.error("博客评论列表导出任务执行异常，请务必及时处理.fileType={}", exportTask.getFileType());
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "博客评论导出文件类型未知");
        }

        TypeReference<List<NameValue<String>>> typeReference = new TypeReference<List<NameValue<String>>>() {};
        List<NameValue<String>> fieldList = JSON.parseObject(exportTask.getFields(), typeReference);

        List<String> fieldNameList = CopyUtils.mapList(fieldList, NameValue::getValue);
        CommentFormVo commentFormVo = JSON.parseObject(exportTask.getParams(), CommentFormVo.class);

        Map<String, Field> fieldMap = this.checkExportFields(fieldNameList);

        /*
            分页获取博客评论信息
         */
        PageDto<CommentDto> pageDto = blogCommentService.getCommentList(commentFormVo);
        List<CommentDto> commentDtoList = pageDto.getList();

        // 数据格式转换
        List<CommentExcel> commentExcelList = CopyUtils.mapList(commentDtoList, this::mapFunction);

        DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");

        try {

            // 导出文件名
            String fileName = TaskTypeEnum.COMMENT_LIST_EXPORT.getName() + "-" + dateFormat.format(new Date())
                    + fileType;

            response.setHeader("Content-disposition", "attachment;filename="
                    + URLEncoder.encode(fileName, StandardCharsets.UTF_8));


            ServletOutputStream outputStream = response.getOutputStream();

            if (type == 0) {
                response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                response.setCharacterEncoding("utf-8");
                /*
                    1、write：设置输出目的和输出结果反射类
                    2、includeColumnFiledNames：设置需要导出的字段列表
                    3、autoCloseStream：不会自动关闭输出流
                    4、sheet：设置文件sheet的名称
                    5、doWrite：开始输出数据
                 */
                EasyExcel.write(outputStream, CommentExcel.class)
                        .includeColumnFieldNames(fieldNameList)
                        .autoCloseStream(Boolean.FALSE)
                        .sheet(TaskTypeEnum.COMMENT_LIST_EXPORT.getName())
                        .doWrite(commentExcelList);
                return;
            }
            // 导出文件类型为：csv或txt
            response.setContentType(type == 1 ? "application/csv;charset=utf-8" : "text/plain;charset=utf-8");

            if (type == 1) {
                /*
                    csv文件
                 */
                outputStream.write(new byte[]{ (byte) 0xEF, (byte) 0xBB,(byte) 0xBF });
            }

            StringBuilder sb = new StringBuilder();

            // 获取线程本地变量属性值
            for (String fieldName : fieldNameList) {
                Field field = fieldMap.get(fieldName);
                ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
                if (excelProperty == null) {
                    log.warn("没有找到属性上使用的注解对象。fieldName={}", fieldName);
                    sb.append("未知").append(',');
                    continue;
                }
                String[] value = excelProperty.value();
                if (value.length != 0) {
                    sb.append(value[value.length - 1]).append(',');
                } else {
                    log.warn("注解对象没有设置属性的名称。fieldName={}", fieldName);
                    sb.append("未知").append(',');
                }
            }
            // 删除最后一个‘,’字符
            sb.deleteCharAt(sb.length() - 1);
            sb.append(System.lineSeparator());
            // 数据写入
            outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));

            for (CommentExcel commentExcel : commentExcelList) {
                // 清空字符串缓存
                sb.delete(0, sb.length());

                for (String fieldName : fieldNameList) {
                    Field field = fieldMap.get(fieldName);
                    try {
                        sb.append(field.get(commentExcel)).append(',');
                    } catch (IllegalAccessException e) {
                        // 理论上不会走到这块异常
                        log.error("获取博客评论对象的‘属性值’失败");
                        sb.append("错误值").append(',');
                    }
                }
                // 删除最后一个‘,’字符
                sb.deleteCharAt(sb.length() - 1);
                sb.append(System.lineSeparator());
                // 数据写入
                outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));
            }

            // 强制执行一次数据写入，以免缓存区数据丢失
            outputStream.flush();

        } catch (Exception e) {
            log.error("博客评论导出异常。message={}", e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 博客评论写入文件方法
     * @param exportTaskMessage 系统任务导出消息对象
     */
    public void blogCommentWriteFile(ExportTaskMessage exportTaskMessage) {
        /*
            获取导出任务记录
         */
        ExportTaskEntity exportTask = exportTaskService.get(exportTaskMessage.getTaskId());

        // 反序列化导出字段列表
        List<String> fieldNameList = JSON.parseArray(exportTask.getFields(), String.class);
        // 博客评论导出请求参数反序列化
        CommentFormVo commentFormVo = JSON.parseObject(exportTask.getParams(), CommentFormVo.class);

        ExportTaskEntity updateExportTask = new ExportTaskEntity();
        updateExportTask.setTaskId(exportTaskMessage.getTaskId());

        /*
            分页查询博客评论信息
         */
        PageDto<CommentDto> pageDto = blogCommentService.getCommentList(commentFormVo);
        if (pageDto.isEmpty()) {
            // 设置任务状态（‘失败’）和备注（‘失败原因’）属性值
            updateExportTask.setStatus(ExportStatusEnum.FAIL.getValue())
                    .setRemark("符合条件的博客评论为空，导出失败。");
            // 操作数据库系统文件导出任务表，更新博客评论导出任务信息
            exportTaskService.saveExportTask(updateExportTask);
            log.info("导出数据为空，结束执行");
            return;
        }

        // 数据格式转换
        List<CommentExcel> commentExcelList = CopyUtils.mapList(pageDto.getList(), this::mapFunction);

        // 导出文件类型
        String fileType; int type;
        if (exportTask.getFileType().equals(FileTypeEnum.XLSX.getValue())) {
            fileType = ".xlsx";
            type = 0;
        } else if (exportTask.getFileType().equals(FileTypeEnum.CSV.getValue())) {
            fileType = ".csv";
            type = 1;
        } else if (exportTask.getFileType().equals(FileTypeEnum.XLS.getValue())) {
            fileType = ".xls";
            type = 0;
        } else if (exportTask.getFileType().equals(FileTypeEnum.TXT.getValue())) {
            fileType = ".txt";
            type = 2;
        } else {
            // 设置任务状态（‘失败’）和备注（‘失败原因’）属性值
            updateExportTask.setStatus(ExportStatusEnum.FAIL.getValue())
                    .setRemark("导出文件类型未知，请联系管理员处理。");
            // 操作数据库系统文件导出任务表，更新博客评论导出任务信息
            exportTaskService.saveExportTask(updateExportTask);
            log.info("博客评论导出文件类型未知，结束执行");
            return;
        }

        Date date = new Date();

        DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");
        DateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyyMMddHHmmss");

        // 导出文件名
        String filePath = downloadProperty.getBasePath() + yyyyMMdd.format(date) + File.separator +
                TaskTypeEnum.COMMENT_LIST_EXPORT.getName() + "-" + yyyyMMddHHmmss.format(date) + fileType;
        File file = new File(filePath);
        boolean bool = file.getParentFile().mkdirs();
        log.info("父级文件夹创建结果：{}", bool);

        // 设置任务状态（‘成功’）和导出开始时间属性值
        updateExportTask.setStatus(ExportStatusEnum.SUCCESS.getValue())
                .setStartTime(date);

        if (type == 0) {
            /*
                导出文件类型为：xlsx或xls
            */
            /*
                1、write：设置输出目的和输出结果反射类
                2、includeColumnFiledNames：设置需要导出的字段列表
                3、sheet：设置文件sheet的名称
                4、doWrite：开始输出数据
            */
            try {
                EasyExcel.write(file, CommentExcel.class)
                        .includeColumnFieldNames(fieldNameList)
                        .sheet(TaskTypeEnum.COMMENT_LIST_EXPORT.getName())
                        .doWrite(commentExcelList);
            } catch (Exception e) {
                log.error("博客评论写入xlsx|xls文件异常。message={}", e.getMessage(), e);
                // 设置任务状态（‘失败’）和备注（‘失败原因’）属性值
                updateExportTask.setStatus(ExportStatusEnum.FAIL.getValue())
                        .setRemark("博客评论导出异常，请联系管理员处理。");
            }

            // 设置任务结束时间
            updateExportTask.setEndTime(new Date());
            // 操作数据库系统文件导出任务表，更新博客评论导出任务信息
            exportTaskService.saveExportTask(updateExportTask);
            log.info("更新导出任务完成，结束执行");
            return;
        }

        try (OutputStream outputStream = Files.newOutputStream(file.toPath())) {
            if (type == 1) {
                // csv文件，要写入文件标识
                outputStream.write(new byte[]{ (byte) 0xEF, (byte) 0xBB,(byte) 0xBF });
            }

            // 获取类下所有属性对象
            Field[] fields = CommentExcel.class.getDeclaredFields();
            // 存储类下所有属性名称
            Map<String, Field> fieldMap = new HashMap<>();
            for (Field field : fields) {
                field.setAccessible(true);

                fieldMap.put(field.getName(), field);
            }

            StringBuilder sb = new StringBuilder();
            for (String fieldName : fieldNameList) {
                Field field = fieldMap.get(fieldName);
                ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
                if (excelProperty == null) {
                    log.warn("没有找到属性上使用的注解对象。fieldName={}", fieldName);
                    sb.append("未知").append(',');
                    continue;
                }
                String[] value = excelProperty.value();
                if (value.length != 0) {
                    // 取数组最后一个值
                    sb.append(value[value.length - 1]).append(',');
                } else {
                    log.warn("注解对象没有设置属性的名称。fieldName={}", fieldName);
                    sb.append("未知").append(',');
                }
            }
            // 删除最后一个‘,’字符
            sb.deleteCharAt(sb.length() - 1);
            sb.append(System.lineSeparator());

            // 写入文件头
            outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));

            for (CommentExcel commentExcel : commentExcelList) {
                // 清空字符串缓存
                sb.delete(0, sb.length());

                for (String fieldName : fieldNameList) {
                    Field field = fieldMap.get(fieldName);
                    try {
                        sb.append(field.get(commentExcel)).append(',');
                    } catch (Exception e) {
                        // 理论上不会走到这块异常
                        log.error("获取博客评论对象的‘属性值’失败。fieldName={} message={}", fieldName, e.getMessage(), e);
                        sb.append("错误值").append(',');
                    }
                }
                // 删除最后一个‘,’字符
                sb.deleteCharAt(sb.length() - 1);
                sb.append(System.lineSeparator());
                // 写入内容
                outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));
            }

            // 最后刷一次写入操作
            outputStream.flush();
        } catch (Exception e) {
            log.error("异步导出博客评论异常。message={}", e.getMessage(), e);
            // 设置任务状态（‘失败’）和备注（‘失败原因’）属性值
            updateExportTask.setStatus(ExportStatusEnum.FAIL.getValue())
                    .setRemark("博客评论导出异常，请联系管理员处理。");
        }

        if (updateExportTask.getStatus().equals(ExportStatusEnum.SUCCESS.getValue())) {
            // 设置文件存储路径和数据行数属性值
            updateExportTask.setFilePath(filePath)
                    .setDataRow(commentExcelList.size());
        }
        // 设置任务结束时间
        updateExportTask.setEndTime(new Date());
        // 操作数据库系统文件导出任务表，更新博客评论导出任务信息
        exportTaskService.saveExportTask(updateExportTask);
        log.info("异步导出博客评论结束");
    }

    /**
     * 校验博客评论数据导出任务参数
     * @param exportTaskVo 导出任务参数对象
     */
    public void validExportCommentParams(ExportTaskVo exportTaskVo) {
        // 获取正在导出自己博客评论列表的用户id
        Long optUserId = SecurityUtils.getUserId();

        // 导出参数反序列化
        CommentFormVo commentFormVo = JSON.parseObject(exportTaskVo.getParams(), CommentFormVo.class);

        Errors errors = new BeanPropertyBindingResult(commentFormVo, CommentFormVo.class.getSimpleName());
        // 校验
        validator.validate(commentFormVo, errors);
        if (errors.hasErrors()) {
            FieldError fieldError = errors.getFieldErrors().get(0);
            log.info("【博客评论数据导出】参数值错误。field={} errorMessage={}", fieldError.getField(),
                    fieldError.getDefaultMessage());
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), fieldError.getDefaultMessage());
        }

        // 设置用户
        commentFormVo.setUserId(optUserId);
        exportTaskVo.setParams(JSON.toJSONString(commentFormVo));

        if (commentFormVo.getLimit() > downloadProperty.getMaxSize()) {
            log.error("导出博客评论数据条数超过最大限制条数。download.size={} maxSize={}",
                    commentFormVo.getPage() * commentFormVo.getLimit(), downloadProperty.getMaxSize());
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "导出数据条数超过最大限制条数");
        }

//        Map<String, String> map = new LinkedHashMap<>();
//        if (StringUtils.isNotBlank(commentFormVo.getWord())) {
//            map.put("评论内容", commentFormVo.getWord());
//        }
//        if (Objects.nonNull(commentFormVo.getBlockId())) {
//            map.put("评论区块", commentFormVo.getBlockId().toString());
//        }
//        List<String> filterParams = new ArrayList<>();
//        if (commentFormVo.getParentUserId() != null && commentFormVo.getParentUserId() == 0) {
//            filterParams.add("一级评论");
//        }
//        if (commentFormVo.getAuthorLike() != null && commentFormVo.getAuthorLike() == 0) {
//            filterParams.add("作者点赞");
//        }
//        if (commentFormVo.getTopFlag() != null && commentFormVo.getTopFlag() == 0) {
//            filterParams.add("已置顶");
//        }
//        if (commentFormVo.getHideFlag() != null && commentFormVo.getHideFlag() == 0) {
//            filterParams.add("已隐藏");
//        }
//        if (commentFormVo.getDeleteFlag() != null && commentFormVo.getDeleteFlag() == 0) {
//            filterParams.add("已删除");
//        }
//        if (!filterParams.isEmpty()) {
//            map.put("过滤条件", String.join(",", filterParams));
//        }

    }

    /**
     * 获取博客评论导出字段映射
     * @param fieldList 导出字段列表
     * @return 博客评论导出模板类属性映射Map
     */
    private Map<String, Field> getExportFieldMap(List<NameValue<String>> fieldList)  {
        // 获取类下所有属性对象
        Field[] fields = CommentExcel.class.getDeclaredFields();
        // 存储类下所有属性名称
        Map<String, Field> fieldMap = new HashMap<>();
        for (Field field : fields) {
            field.setAccessible(true);

            fieldMap.put(field.getName(), field);
        }
        // 导出字段校验
        for (NameValue<String> field : fieldList) {
            if (!fieldMap.containsKey(field.getValue())) {
                throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "导出字段列表中存在非法字段，请确认，如有问题，请联系管理员处理。");
            }
        }
        return fieldMap;
    }

    /**
     * 博客评论导出字段列表校验方法
     * @param fieldList 导出的字段列表
     * @return 博客评论导出模板类属性映射Map
     */
    private Map<String, Field> checkExportFields(List<String> fieldList)  {
        // 获取类下所有属性对象
        Field[] fields = CommentExcel.class.getDeclaredFields();
        // 存储类下所有属性名称
        Map<String, Field> fieldMap = new HashMap<>();
        for (Field field : fields) {
            field.setAccessible(true);

            fieldMap.put(field.getName(), field);
        }
        // 导出字段校验
        for (String fieldName : fieldList) {
            if (!fieldMap.containsKey(fieldName)) {
                throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(),
                        "导出字段列表中存在非法字段，请确认，如有问题，请联系管理员处理。");
            }
        }
        return fieldMap;
    }

    /**
     * 获取博客评论数据格式转换方法
     * @return 博客评论数据格式转换方法
     */
    public CommentExcel mapFunction(CommentDto commentDto) {
        CommentExcel commentExcel = new CommentExcel();
        commentExcel.setContent(commentDto.getContent());
        commentExcel.setUserName(commentDto.getUserName());
        commentExcel.setBlockId(commentDto.getBlockId());
        commentExcel.setOneCommentFlag(
                commentDto.getOneCommentFlag().equals(CommonEnum.ZERO.getStringValue()) ?
                        "顶级评论" : "回复评论"
        );
        commentExcel.setLikeNum(commentDto.getLikeNum());
        commentExcel.setReplyNum(commentDto.getReplyNum());
        commentExcel.setAuthorLike(
                commentDto.getAuthorLike().equals(CommentEnum.AUTHOR_LIKE.getValue()) ? "点赞" : "正常"
        );
        commentExcel.setTopFlag(commentDto.getTopFlag().equals(CommentEnum.TOP_YES.getValue()) ? "置顶" : "正常");
        commentExcel.setHideFlag(commentDto.getHideFlag().equals(CommentEnum.HIDE_NO.getValue()) ? "正常" : "隐藏");
        commentExcel.setDeleteFlag(commentDto.getDeleteFlag().equals(CommonEnum.ZERO.getValue()) ? "正常" : "删除");
        commentExcel.setCreateTime(TimeUtils.format(commentDto.getCreateTime()));
        commentExcel.setRemark(commentDto.getRemark());

        return commentExcel;
    }
}
